package main

import (
	"bytes"
	"context"
	"crypto/tls"
	"fmt"
	"golang.org/x/net/http2"
	"io"
	"log"
	"net/http"
	"os"
	"route"
	"time"
)

const (
	httpsPort = "443"
	httpPort  = "80"
)

var (
	listenAddr = ""
	// listenPort = "" // https 默认 443

	certName = ""
	keyName  = ""
)

func init() {
	os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1")
}

func main() {
	// 对 http 的访问都会重映射到 { 对应的 https 页面 OR home(redirect-page) 页面 }
	httpServer := &http.Server{
		Addr:         listenAddr + ":" + httpPort,
		WriteTimeout: 5 * time.Second,
		ReadTimeout:  5 * time.Second,
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Connection", "close")
			tourl := "https://" + r.Host + r.RequestURI
			http.Redirect(w, r, tourl, http.StatusMovedPermanently)
		}),
		// OR // http.RedirectHandler("https://"+ListenAddr+"/home/", http.StatusMovedPermanently),
	}
	go func() { log.Println(httpServer.ListenAndServe()) }()

	// 开启 http2
	for {
		httpsServer := &http.Server{
			// NOTE: 关闭 HTTP 2 响应
			// TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},

			//  Handler: route.NewRouter(60 * 60 * 24 * 180), // 180 天
			Addr: listenAddr + ":" + httpsPort,

			WriteTimeout: 30 * time.Second,
			ReadTimeout:  30 * time.Second,
			IdleTimeout:  60 * time.Second,

			// tls 加密使用的选项
			TLSConfig: &tls.Config{
				// 控制服务器使用已配置的 cipherSuites 顺序
				PreferServerCipherSuites: true,

				// ECDHE - 椭圆曲线优先顺序
				CurvePreferences: []tls.CurveID{
					tls.CurveP256,
					tls.X25519, // At Least golang 1.8
				},

				// tls/ssl 协议最低为 tls1.1
				MinVersion: tls.VersionTLS11,

				// NOTE: 加密套件; 不配置, 否则 MinVersion 就无效了 orz
				// CipherSuites: []uint16{
				// 	tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
				// 	tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
				// 	tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, // At least 1.8
				// 	tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,   // At least 1.8
				// 	tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
				// 	tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,

				// 	// 部分客户端可能需要, 但建议关闭
				// 	// tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
				// 	// tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
				// },
			},
			//ConnState: func (net.Conn, ConnState){/* 控制连接状态, 怎么用? */},
		}

		// 证书更新部分
		shouldClose := make(chan bool)

		handler, rch, closefunc := route.NewRouter()
		httpsServer.Handler = handler

		go func() {
			for rfunc := range rch {
				closefunc()
				r := rfunc()
				// 从 chan 获得证书
				newCertFile, _, cerr := r.FormFile("cert")
				newKeyFile, _, kerr := r.FormFile("key")

				if cerr != nil || kerr != nil {
					log.Fatalln(cerr, kerr)
					return
				}

				httpsServer.RegisterOnShutdown(func() {
					// 打开原证书
					certFile, cerr := os.OpenFile(certName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
					keyFile, kerr := os.OpenFile(keyName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
					if cerr != nil || kerr != nil {
						log.Fatalln(cerr, kerr)
						return
					}
					defer newCertFile.Close()
					defer newKeyFile.Close()
					defer certFile.Close()
					defer keyFile.Close()

					// 覆盖原证书
					cerr = ioRead2Write(newCertFile, certFile)
					kerr = ioRead2Write(newKeyFile, keyFile)
					if cerr != nil || kerr != nil {
						log.Fatalln(cerr, kerr)
						return
					}

					// 关闭 httpsServer, 重新绑定 ip/端口, 重启服务器
					shouldClose <- true
				})

				// shutdown 服务
				httpsServer.Shutdown(context.Background())
			}
		}()

		// 配置 HTTP2.0 支持
		http2.ConfigureServer(httpsServer, &http2.Server{})

		log.SetFlags(log.Llongfile)

		log.Println("Listen on", listenAddr+":"+httpPort)
		log.Println(httpsServer.ListenAndServeTLS(certName, keyName))

		for range shouldClose {
			break
		}
		httpsServer.Close()
	}
}

// 读 io.Reader, 写 io.Writer
func ioRead2Write(reader io.Reader, writer io.Writer) error {
	buf := bytes.NewBuffer(make([]byte, 0, 2048)) // 3072 ?
	if n1, err := buf.ReadFrom(reader); err != nil {
		return err
	} else {
		if n2, err := buf.WriteTo(writer); err != nil {
			return err
		} else if n2 != n1 {
			return fmt.Errorf("Is there insufficient space on the hard disk(硬盘空间不足)?")
		}
	}
	return nil
}
